package com.qiyuan.boot.web.service.impl;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.qiyuan.boot.utils.BusinessException;
import com.qiyuan.boot.utils.ConstUtil;
import com.qiyuan.boot.web.bean.Article;
import com.qiyuan.boot.web.bean.Manager;
import com.qiyuan.boot.web.bean.vo.TimeLineVo;
import com.qiyuan.boot.web.mapper.ArticleMapper;
import com.qiyuan.boot.web.repository.ArticleRepository;
import com.qiyuan.boot.web.service.ArticleService;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author qiyuan
 */
@Service
@Transactional(rollbackFor=BusinessException.class)
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Resource
    private ArticleMapper articleMapper;
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Resource
    private ArticleRepository articleRepository;



    @Override
    public void updateArticle(Article article) throws BusinessException {
        try {
            Manager manager = (Manager) SecurityUtils.getSubject().getPrincipal();
            if(manager == null){
                throw new BusinessException("当前用户未登录");
            }else{
                article.setManagerName(manager.getName());
            }

            //验证文章
//            QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
//            queryWrapper.lambda().eq(Article::getArticleName,article.getArticleName())
//                    .ne(article.getId() != null,Article::getId,article.getId());
//            int count = articleMapper.selectCount(queryWrapper);
//
//            if (count>0){
//                throw new  BusinessException("存在相同题目的文章");
//            }
            //设置默认值
            article.setGood("1");//好文
            article.setRecommend("0");//推荐
            article.setOfficial("0");//官方
            article.setTop("0");//置顶
            article.setBoutique("0");//精品
            if (null != article.getId()){
                //修改
                article.setUpdateTime(new Date());
                article.setUpdateBy(article.getManagerName());
                articleMapper.updateById(article);
            }else {
                //添加
                article.setCreate_time(new Date());
                article.setCreateBy(article.getManagerName());
                articleMapper.insert(article);
            }
            if (ConstUtil.ARTICLE_ID !=article.getId()){
                //把数据存入索引中，文章id为1时是介绍自己
                if ("true".equals(article.getDisclosure())){
                    articleRepository.save(article);
                }
            }
        } catch (BusinessException e) {
            throw new BusinessException(e.getErrorMessage());
        }
    }

    @Override
    public void deleteArticle(List<Integer> ids) throws BusinessException{
        try {
            if(ids.contains(ConstUtil.ARTICLE_ID)){
                throw new  BusinessException("自我介绍不能删除");
            }
            List<Article> articles = articleMapper.selectBatchIds(ids);
            //删除索引
            articleRepository.deleteAll(articles);
            //删除文章
            this.removeByIds(ids);
        } catch (BusinessException e) {
            throw new BusinessException(e.getErrorMessage());
        }
    }

    /**
     * 查询文章列表
     * @param article 通过各个条件查询全部
     * @return
     */
    @Override
    public List<Article> getArticleByPage(Article article)  {
        Subject subject = SecurityUtils.getSubject();
        Manager manager = (Manager)subject.getPrincipal();
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
        if (null != manager){
            if ("true".equals(manager.getArticleStatus())){
                //只能看到自己的数据
                queryWrapper.lambda().eq(null!=article.getArticleType(),Article::getArticleType,article.getArticleType())
                        .eq(null != article.getTopRecommend(),Article::getTopRecommend,article.getTopRecommend())
                        .eq(Article::getArticleState,"0")
                        .eq(Article::getManagerId,manager.getId())
                        .like(StringUtils.isNotBlank(article.getArticleName()), Article::getArticleName, article.getArticleName())
                        .ne(Article::getId, ConstUtil.ARTICLE_ID)
                        .orderByDesc(Article::getTop)
                        .orderByDesc(Article::getArticleReadCount)
                        .orderByDesc(article.isFavorite(),Article::getArticleStarNum)
                        .orderByDesc(article.isCommentMost(),Article::getArticleConNum)
                        .orderByDesc(Article::getCreateTime);
                return articleMapper.selectList(queryWrapper);
            }else{
                // 只能看到自己公开或者不公开的，还有别人公开的数据
                QueryWrapper<Article> queryMangeDisWrapper = new QueryWrapper<>();
                queryMangeDisWrapper.lambda().eq(null!=article.getArticleType(),Article::getArticleType,article.getArticleType())
                        .eq(null != article.getTopRecommend(),Article::getTopRecommend,article.getTopRecommend())
                        .eq(Article::getArticleState,"0")
                        .eq(Article::getDisclosure,"true")
                        .like(StringUtils.isNotBlank(article.getArticleName()), Article::getArticleName, article.getArticleName())
                        .ne(Article::getId, ConstUtil.ARTICLE_ID);
                List<Article> articleList = articleMapper.selectList(queryMangeDisWrapper);
                QueryWrapper<Article> queryMangeWrapper = new QueryWrapper<>();
                queryMangeWrapper.lambda().eq(null!=article.getArticleType(),Article::getArticleType,article.getArticleType())
                        .eq(null != article.getTopRecommend(),Article::getTopRecommend,article.getTopRecommend())
                        .eq(Article::getArticleState,"0")
                        .eq(Article::getManagerId,manager.getId())
                        .like(StringUtils.isNotBlank(article.getArticleName()), Article::getArticleName, article.getArticleName())
                        .ne(Article::getId, ConstUtil.ARTICLE_ID);
                List<Article> articleManageList = articleMapper.selectList(queryMangeWrapper);
                List<Article> result = Stream.of(articleList, articleManageList).flatMap(Collection::stream).distinct().collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(result)){
                    result = result.stream().sorted(Comparator.comparing(Article::getCreateTime).reversed()).collect(Collectors.toList());//根据创建时间倒排
                }
                return result;
            }
        }else{
            //只能看到公开的数据
            queryWrapper.lambda().eq(null!=article.getArticleType(),Article::getArticleType,article.getArticleType())
                                 .eq(null != article.getTopRecommend(),Article::getTopRecommend,article.getTopRecommend())
                                 .eq(Article::getArticleState,"0")
                                 .eq(Article::getDisclosure,"true")
                                 .like(StringUtils.isNotBlank(article.getArticleName()), Article::getArticleName, article.getArticleName())
                                 .ne(Article::getId, ConstUtil.ARTICLE_ID)
                                 .orderByDesc(Article::getTop)
                                 .orderByDesc(Article::getArticleReadCount)
                                 .orderByDesc(article.isFavorite(),Article::getArticleStarNum)
                                 .orderByDesc(article.isCommentMost(),Article::getArticleConNum)
                                 .orderByDesc(Article::getCreateTime);
            return articleMapper.selectList(queryWrapper);
        }
    }

    @Override
    public Article searchById(int id){
        Subject subject = SecurityUtils.getSubject();
        Manager manager = (Manager)subject.getPrincipal();
        Article article = articleMapper.selectById(id);
        if (null != manager){
            if ("true".equals(manager.getArticleStatus())){
                if (article.getManagerId().equals(manager.getId())){
                    //阅读量加一
                    article.setArticleReadCount(article.getArticleReadCount()+1);
                    articleMapper.updateById(article);
                    return article;
                }
            }else{
                if (article.getManagerId().equals(manager.getId())){
                    //阅读量加一
                    article.setArticleReadCount(article.getArticleReadCount()+1);
                    articleMapper.updateById(article);
                    return article;
                }else if ("true".equals(article.getDisclosure())){
                    //阅读量加一
                    article.setArticleReadCount(article.getArticleReadCount()+1);
                    articleMapper.updateById(article);
                    return article;
                }
            }
        }else if ("true".equals(article.getDisclosure())){
            //阅读量加一
            article.setArticleReadCount(article.getArticleReadCount()+1);
            articleMapper.updateById(article);
            return article;
        }
        return null;

    }

    @Override
    public List<Article> getArticleList(String keywords,Integer currentPage,Integer pageSize) {

        //分页
        Pageable pageable = PageRequest.of(currentPage - 1, pageSize);
        // 高亮条件
        HighlightBuilder highlightBuilder = getHighlightBuilder("articleName", "articleContent","articleRemark");
        //先查询es索引
        NativeSearchQuery builder = new NativeSearchQueryBuilder()
                //构建模糊查询多条件,boost即为权重，数值越大，权重越大
                .withQuery(QueryBuilders.boolQuery().should(QueryBuilders.fuzzyQuery("articleTag",keywords).boost(1))
                                                    .should(QueryBuilders.fuzzyQuery("articleRemark",keywords).boost(2))
                                                    .should(QueryBuilders.fuzzyQuery("articleContent",keywords).boost(2))
                                                    .should(QueryBuilders.matchQuery("articleName",keywords).boost(3))
                                                    .should(QueryBuilders.matchQuery("managerName",keywords).boost(1)))
                .withHighlightBuilder(highlightBuilder)
                .withPageable(pageable)
                .build();
        //IndexCoordinates多个索引组合查询，不加是单个，根据class进行查找索引
        SearchHits<Article> searchHits = elasticsearchRestTemplate.search(builder, Article.class, IndexCoordinates.of("article"));
        // 高亮字段映射
        List<Article> articleList = Lists.newArrayList();
        for (SearchHit<Article> searchHit : searchHits) {
            Article content = searchHit.getContent();
            Article articleVo = new Article();
            BeanUtils.copyProperties(content,articleVo);
            Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
            for (String highlightField : highlightFields.keySet()) {
                if (StringUtils.equals(highlightField,"articleName")){
                    articleVo.setArticleName(highlightFields.get(highlightField).get(0));
                }else if(StringUtils.equals(highlightField,"articleContent")){
                    articleVo.setArticleContent(highlightFields.get(highlightField).get(0));
                }else if(StringUtils.equals(highlightField,"articleRemark")){
                    articleVo.setArticleRemark(highlightFields.get(highlightField).get(0));
                }else if(StringUtils.equals(highlightField,"articleTag")){
                    articleVo.setArticleTag(highlightFields.get(highlightField).get(0));
                }else if(StringUtils.equals(highlightField,"managerName")){
                    articleVo.setManagerName(highlightFields.get(highlightField).get(0));
                }
            }
            articleList.add(articleVo);
        }
        if (articleList.size() > 0){
            return articleList;
        }
        return null;
    }

    /**
     * 设置高亮字段
     * @param fields
     * @return
     */
    private HighlightBuilder getHighlightBuilder(String... fields) {
        //生成高亮查询器
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        for (String field : fields) {
            //高亮查询字段
            highlightBuilder.field(field);
        }
        //如果要多个字段高亮,这项要为false
        highlightBuilder.requireFieldMatch(false);
        //高亮设置
        highlightBuilder.preTags("<span style='color:red;font-weight:bold'>");
        highlightBuilder.postTags("</span>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        //最大高亮分片数
        highlightBuilder.fragmentSize(800000);
        //从第一个分片获取高亮片段
        highlightBuilder.numOfFragments(0);
        return highlightBuilder;
    }


    @Override
    public List<String> searchTag() {
        List<String> tags = articleMapper.searchTag();
        List<String> articleTags = new ArrayList<>();
        for(String str:tags){
            ArrayList<String> strings = Lists.newArrayList(str.split(","));
            articleTags.addAll(strings);
        }
        //去重
        return articleTags.stream().distinct().collect(Collectors.toList());
    }

    @Override
    public List<TimeLineVo> searchTimeLine() {
        List<Article> articles = articleMapper.searchTimeLine();
        List<TimeLineVo> lineVos = new ArrayList<>();
        //取出时间
        List<String> yearTimesList = articles.stream().map(Article::getCreateTime).collect(Collectors.toList());
        //时间转换为年份
        List<Integer> years = yearTimesList.stream().map(x ->DateUtil.year(DateUtil.parse(x))).collect(Collectors.toList());
        //年份去重
        List<Integer> collect = years.stream().distinct().collect(Collectors.toList());
        for (Integer yearTimes : collect){
            TimeLineVo timeLineVo = new TimeLineVo();
            //对文章集合中的时间与年份匹配
            List<Article> articleList = articles.stream().filter(item -> item.getCreateTime().contains(yearTimes.toString())).collect(Collectors.toList());
            timeLineVo.setYear(yearTimes.toString());
            timeLineVo.setArticle(articleList);
            lineVos.add(timeLineVo);
        }
        return lineVos;
    }

    @Override
    public Article searchByArticleIdAndManagerId(int articleId, int managerId) {
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(Article::getId,articleId)
                             .eq(Article::getManagerId,managerId)
                                .eq(Article::getArticleState,ConstUtil.OTHER_STATE);
        Article article = articleMapper.selectOne(queryWrapper);
        Manager manager = (Manager) SecurityUtils.getSubject().getPrincipal();
        if(!manager.getId().equals(article.getManagerId())){
            throw new BusinessException("当前登录人和文章博主不一致");
        }

        return article;
    }

    /**
     * 文章点赞
     * @param id
     */
    @Override
    public void like(Integer id) {
        Article article = articleMapper.selectById(id);
        if (null != article){
            if (null == article.getArticleStarNum() || article.getArticleStarNum() == 0){
                article.setArticleStarNum(1);
            }else{
                article.setArticleStarNum(article.getArticleStarNum()+1);
            }
            articleMapper.updateById(article);
        }
    }

}
